package simulation;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * Factory imports info from data files.
 * 
 * @author Robert C. Duvall
 */
public class Factory {
    // data file keywords
    /**
     * Index of gravity constant
     */
    public static final int GRAVITY_INDEX = 1;
    /**
     * Index of viscosity constant
     */
    public static final int VISCOSITY_INDEX = 2;
    /**
     * Index of center of mass constant
     */
    public static final int CENTER_OF_MASS_INDEX = 3;
    /**
     * Index of wall repulsion constant
     */
    public static final int WALL_REPULSION_INDEX = 4;
    /**
     * wallID for top wall
     */
    public static final int TOP_WALL = 1;
    /**
     * wallID for right wall
     */
    public static final int RIGHT_WALL = 2;
    /**
     * wallID for bottom wall
     */
    public static final int BOTTOM_WALL = 3;
    /**
     * wallID for left wall
     */
    public static final int LEFT_WALL = 4;

    
    private static final String MASS_KEYWORD = "mass";
    private static final String SPRING_KEYWORD = "spring";
    private static final String MUSCLE_KEYWORD = "muscle";
    private static final String GRAVITY_KEYWORD = "gravity";
    private static final String CENTER_OF_MASS_KEYWORD = "centermass";
    private static final String VISCOSITY_KEYWORD = "viscosity";
    private static final String WALLREPULSION_KEYWORD = "wall";

    // mass IDs
    /** */
    private Map<Integer, Mass> myMasses = new HashMap<Integer, Mass>();

    /** */
    private ArrayList<DataInput> myDataInputs = new ArrayList<DataInput>();

    /**
     * Constructs a Factory
     * 
     * @param model 
     */
    public Factory(final Model model) {
        myDataInputs.add(new DataInput(MASS_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                int id = line.nextInt();
                double x = line.nextDouble();
                double y = line.nextDouble();
                double mass = line.nextDouble();
                Mass result;
                if (mass > 0) {
                    result = new Mass(x, y, mass, model);
                } 
                else {
                    result = new FixedMass(x, y, mass, model);
                }
                myMasses.put(id, result);
                model.add(result);
            }
        });
        createDataInputs();
    }

    /**
     * Sets forces and creates masses and springs based on data file.
     */
    private final void createDataInputs() {
        createMuscles();
        createSprings();
        createGravityForce();
        createCenterOfMassForce();
        createViscosityForce();
        createWallRepulsionForce();
    }

    private void createCenterOfMassForce() {
        myDataInputs.add(new DataInput(CENTER_OF_MASS_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.addForce(new CenterOfMassForce(line.nextDouble(), line.nextDouble(), 
                                                     CENTER_OF_MASS_INDEX));
            }
        });
    }

    private void createWallRepulsionForce() {
        myDataInputs.add(new DataInput(WALLREPULSION_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.addForce(new WallRepulsionForce(WALL_REPULSION_INDEX, line.nextDouble(),
                                                      line.nextDouble(), line.nextInt()));
            }
        });
    }

    private void createViscosityForce() {
        myDataInputs.add(new DataInput(VISCOSITY_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.addForce(new ViscocityForce(VISCOSITY_INDEX, line.nextDouble()));
            }
        });
    }

    private void createGravityForce() {
        myDataInputs.add(new DataInput(GRAVITY_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.addForce(new GravityForce(GRAVITY_INDEX, line.nextDouble(),
                               line.nextDouble()));
            }
        });
    }

    private void createSprings() {
        myDataInputs.add(new DataInput(SPRING_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.add(new Spring(myMasses.get(line.nextInt()), myMasses.get(line.nextInt()),
                          line.nextDouble(), line.nextDouble()));
            }
        });
    }

    private void createMuscles() {
        myDataInputs.add(new DataInput(MUSCLE_KEYWORD) {
            @Override
            public void readIn(final Scanner line, final Model model) {
                model.add(new Muscle(myMasses.get(line.nextInt()), myMasses.get(line.nextInt()),
                          line.nextDouble(), line.nextDouble(), line.nextDouble()));
            }
        });
    }

    /**
     * loads data from .exp files.
     * 
     * @param model
     *            is the screen where simulation drawn
     * @param modelFile
     *            specifies file where data is input
     */
    public final void load(final Model model, final File modelFile) {
        try {
            Scanner input = new Scanner(modelFile);
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();
                    for (DataInput di : myDataInputs) {
                        if (type.equals(di.getMyName())) {
                            di.readIn(line, model);
                        }
                    }
                }
            }
            input.close();
        } 
        catch (FileNotFoundException e) {
            // should not happen because File came from user selection
            e.printStackTrace();
        }
    }
}
